#    Copyright 2012 New Mexico EPSCoR
#
#    This file is part of Teacher's P.E.T.
#
#    Teacher's P.E.T. is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Teacher's P.E.T. is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Teacher's P.E.T..  If not, see <http://www.gnu.org/licenses/>.

from django.db import models
from django import forms
from django.contrib.auth.models import User
from django.utils.safestring import mark_safe
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.http import Http404

from apps.course.models import Course
from apps.participants.models import CourseParticipant
from tags.templatetags.natural_sort import natural_sort

from itertools import chain
import logging
import datetime

logger = logging.getLogger('pet.apps.forum.models')

class EmptyPaginator(Paginator):
	def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True):
		super(EmptyPaginator,self).__init__(object_list, per_page, orphans, allow_empty_first_page)
		if not object_list: self.object_list = []

class Forum(models.Model):
	title = models.CharField(max_length=1024)
	
	def __unicode__(self):
		RetStr = self.title + " ("
		for course in self.courses.all():
			RetStr += course.course_id
		return RetStr + ")"

def unread_messages(self):
	return self.received_messages.filter(send_date__isnull=False,read_date__isnull=True)

def draft_messages(self):
	return ForumPrivateMessage.objects.filter(sender=self,send_date__isnull=True)

Course.add_to_class('forum',models.ForeignKey(Forum,related_name="courses",blank=True,null=True,on_delete=models.SET_NULL))
CourseParticipant.add_to_class('forum_entries_per_page',models.PositiveIntegerField(default=10))
CourseParticipant.add_to_class('email_new_messages',models.BooleanField(default=False))
User.add_to_class('unread_messages',property(unread_messages))
User.add_to_class('draft_messages',property(draft_messages))

class AttachForumForm(forms.ModelForm):
	class Meta:
		model = Course
		fields = ['forum']
		
class ForumThread(models.Model):
	forum = models.ForeignKey(Forum,related_name="threads")
	title = models.CharField(max_length=512)
	latest_instructor_reply = models.ForeignKey('ForumEntry',related_name='+',null=True,blank=True)
	latest_assistant_reply = models.ForeignKey('ForumEntry',related_name='+',null=True,blank=True)
	flagged_as_sticky = models.BooleanField(default=False)
	flagged_as_locked = models.BooleanField(default=False)
	flagged_as_deleted = models.BooleanField(default=False)
	first_entry = models.ForeignKey('ForumEntry',related_name='+',blank=True,null=True)
	last_entry = models.ForeignKey('ForumEntry',related_name='+',blank=True,null=True)
	
	def __unicode__(self):
		return "%s - %s (%s)" % (self.forum, self.title, self.first_entry.author.user if self.first_entry else "No first entry found!")
	
	class Meta:
		ordering = ['-is_sticky', '-last_entry__creation_date', '-first_entry__creation_date']

	def is_sticky(self,desired=True):
		return self.flagged_as_sticky == desired 
	def is_locked(self,desired=True):
		return self.flagged_as_locked == desired 
	def is_deleted(self,desired=True):
		return self.flagged_as_deleted == desired
	def is_hidden(self,desired=True):
		return self.flagged_as_deleted == desired
	
	def mark_locked(self):
		self.flagged_as_locked = True
	def mark_unlocked(self):
		self.flagged_as_locked = False
	def mark_sticky(self):
		self.flagged_as_sticky = True
	def mark_unsticky(self):
		self.flagged_as_sticky = False
	def mark_deleted(self):
		self.flagged_as_deleted = True
	def mark_undeleted(self):
		self.flagged_as_deleted = False
	def mark_hidden(self):
		self.flagged_as_deleted = True
	def mark_unhidden(self):
		self.flagged_as_deleted = False
		
	@staticmethod
	def find(forum=None,id=None,title=None,is_sticky=None,is_locked=None,is_deleted=None,keep_queryset=False):
		logger.find_fxn('forum %s,id %s,title %s,is_sticky %s,is_locked %s, is_deleted %s, keep_queryset %s', forum,id,title,is_sticky,is_locked,is_deleted,keep_queryset)
		Objects = ForumThread.objects.all()
		if forum: Objects = Objects.filter(forum=forum)
		if id: Objects = Objects.filter(pk=id)
		if title: Objects = Objects.filter(title=title)
		if is_sticky is not None: Objects = Objects.filter(flagged_as_sticky=is_sticky)
		if is_locked is not None: Objects = Objects.filter(flagged_as_locked=is_locked)
		if is_deleted is not None: Objects = Objects.filter(flagged_as_deleted=is_deleted)
		if Objects:
			if len(Objects) == 1 and not keep_queryset: return Objects[0]
			return Objects
		return None
	@staticmethod
	def find_or_404(forum=None,id=None,title=None,is_sticky=None,is_locked=None,is_deleted=None,keep_queryset=False):
		Results = ForumThread.find(forum,id,title,is_sticky,is_locked,is_deleted,keep_queryset)
		if not Results:
			logger.http404('ForumThread not found')
			raise Http404
		return Results
	
	class Meta:
		unique_together = ['forum','title']
class ForumThreadCreateForm(forms.ModelForm):
	class Meta:
		model = ForumThread
		fields = ['title']
	def __init__(self, forum, *args, **kwargs):
		self.forum = forum or None
		super(ForumThreadCreateForm,self).__init__(*args,**kwargs)
	def clean_title(self):
		title = self.cleaned_data['title']
		if self.forum and ForumThread.objects.filter(forum=self.forum,title=title):
			raise forms.ValidationError("A thread with this title already exists in the forum")
		return title
	
class ForumEntry(models.Model):
	thread = models.ForeignKey(ForumThread, related_name="entries")
	quoted_entry = models.TextField(blank=True,null=True)
	entry_number = models.PositiveIntegerField(default=0)
	author = models.ForeignKey(CourseParticipant)
	entry = models.TextField()
	creation_date = models.DateTimeField(auto_now_add=True)
	edited_date = models.DateTimeField(blank=True,null=True)
	flagged_for_moderator = models.BooleanField(default=False)
	flagged_as_deleted = models.BooleanField(default=False)
	
	def __unicode__(self):
		return "%s - %s %s (%s) - %s" % (self.thread.forum, self.thread.title, self.entry_number, self.author.user, self.entry)
	class Meta:
		ordering = ['entry_number']
		
	def is_marked_for_moderator(self,desired=True):
		return self.flagged_for_moderator == desired 
	def is_deleted(self,desired=True):
		return self.flagged_as_deleted == desired 
	def is_hidden(self,desired=True):
		return self.flagged_as_deleted == desired
	
	def mark_deleted(self):
		self.flagged_as_deleted = True
	def mark_undeleted(self):
		self.flagged_as_deleted = False
	def mark_flagged_for_moderator(self):
		self.flagged_for_moderator = True
	def mark_reviewed_by_moderator(self):
		self.flagged_for_moderator = False
	def mark_hidden(self):
		self.flagged_as_deleted = True
	def mark_unhidden(self):
		self.flagged_as_deleted = False
		
	def date(self):
		if not self.edited_date: return self.creation_date
		else: return self.edited_date
	
	def save(self,*args, **kwargs):
		if self.thread and self.pk is None:
			self.entry_number = self.thread.entries.count()
		super(ForumEntry,self).save(*args,**kwargs)
		courses = self.thread.forum.courses.all()
		is_instructor = False
		is_assistant = False
		for course in courses:
			CP = CourseParticipant.find(course,self.author.user)
			if CP and CP.status == "INSTRUCTOR": is_instructor = True
			if CP and CP.status == "ASSISTANT": is_assistant = True
		if is_instructor: self.thread.latest_instructor_reply = self; self.thread.save()
		elif is_assistant: self.thread.latest_assistant_reply = self; self.thread.save()
		
	@staticmethod
	def find(thread=None,id=None,entry_number=None,author=None,flagged_for_moderator=None,flagged_as_deleted=None,keep_queryset=False):
		logger.find_fxn('thread %s,id %s,entry_number %s,author %s,flagged_for_moderator %s,flagged_as_deleted %s,keep_queryset %s', thread,id,entry_number,author,flagged_for_moderator,flagged_as_deleted,keep_queryset)
		Objects = ForumEntry.objects.all()
		if thread: Objects = Objects.filter(thread=thread)
		if id: Objects = Objects.filter(pk=id)
		if entry_number: Objects = Objects.filter(entry_number=entry_number)
		if author: Objects = Objects.filter(author=author)
		if flagged_for_moderator is not None: Objects = Objects.filter(flagged_for_moderator=flagged_for_moderator)
		if flagged_as_deleted is not None: Objects = Objects.filter(flagged_as_deleted=flagged_as_deleted)
		if Objects:
			if len(Objects) == 1 and not keep_queryset: return Objects[0]
			return Objects
		return None
	@staticmethod
	def find_or_404(thread=None,id=None,entry_number=None,author=None,flagged_for_moderator=None,flagged_as_deleted=None,keep_queryset=False):
		Results = ForumEntry.find(thread,id,entry_number,author,flagged_for_moderator,flagged_as_deleted,keep_queryset)
		if not Results:
			logger.http404('ForumEntry not found')
			raise Http404
		return Results
class ForumEntryEditForm(forms.ModelForm):
	class Meta:
		model = ForumEntry
		fields = ['entry']
		widgets = {
			'entry': forms.Textarea(attrs={'cols': 80, 'rows': 20}),
		}

class ForumEntryLike(models.Model):
	course_participant = models.ForeignKey(CourseParticipant)
	entry = models.ForeignKey(ForumEntry, related_name="likes")
	
class ForumBan(models.Model):
	NO_BAN = 0
	BAN_POST_ATTACHMENTS = 20 # No adding/deleting attachments from posts
	BAN_POSTS = 30 # No creating/editing posts
	BAN_FORUM = 50 # No Forum access at all
	BAN_MESSAGE_ATTACHMENTS = 10 # No attaching files to any messages
	BAN_NEW_MESSAGES = 20 # No creating new messages (can still reply to messages)
	BAN_REPLIES = 30 # No creating any messages (new or replies)
	BAN_MESSAGES = 50 # No Message access at all
	FORUM_BANS = (
		(NO_BAN,'No Bans - User has unrestricted access to forums.'),
		(BAN_POST_ATTACHMENTS,'Banned from Attaching - User cannot attach files to any posts.'),
		(BAN_POSTS,'Banned from Posting - User cannot make new posts or edit current posts. User can still thank/flag posts.'),
		(BAN_FORUM,'Banned from Forum - User is no longer allowed access to the Forum.'),
	)
	MESSAGE_BANS = (
		(NO_BAN,'No Bans - User has unrestricted access to messages.'),
		(BAN_MESSAGE_ATTACHMENTS,"Banned from Attaching Files - User cannot attach files to any messages."),
		(BAN_NEW_MESSAGES,"Banned from New Messages - User cannot create new messages. User can still reply to messages. User cannot attach files to any messages."),
		(BAN_REPLIES,"Banned from Creating Any Messages - User cannot create new messages or reply to others. User can still view received messages."),
		(BAN_MESSAGES,"Banned from Messages - User is no longer allowed access to Messages."),
	)
	course_participant = models.ForeignKey(CourseParticipant)
	forum = models.ForeignKey(Forum)
	forum_ban = models.PositiveIntegerField(choices=FORUM_BANS,default=0,help_text="Restriction levels in order from least restrictive to most restrictive")
	message_ban = models.PositiveIntegerField(choices=MESSAGE_BANS,default=0,help_text="Restriction levels in order from least restrictive to most restrictive")
class ForumBanForm(forms.ModelForm):
	class Meta:
		model = ForumBan
		fields = ['forum_ban','message_ban']

class ForumPrivateMessageBlock(models.Model):
	blocker = models.ForeignKey(User,related_name="block_list")
	blocked = models.ForeignKey(User,related_name='+')
	
	def __unicode__(self):
		return "%s blocking %s" % (self.blocker, self.blocked)

class ForumPrivateMessage(models.Model):
	forum = models.ForeignKey(Forum)
	recipient = models.ForeignKey(User,related_name="received_messages")
	sender = models.ForeignKey(User,related_name="sent_messages")
	message = models.TextField()
	subject = models.CharField(max_length=512)
	response_to = models.ForeignKey('self',related_name="replies",blank=True,null=True)
	
	send_date = models.DateTimeField(blank=True,null=True)
	flagged_date = models.DateTimeField(blank=True,null=True)
	read_date = models.DateTimeField(blank=True,null=True)
	deleted_date = models.DateTimeField(blank=True,null=True)
	hide_date = models.DateTimeField(blank=True,null=True)
	
	def __unicode__(self):
		return "%s - %s => %s" % (self.sender.username, self.subject, self.recipient)
	
	@property
	def sent_today(self):
		dt = datetime.datetime.now()
		if self.send_date and self.send_date >= datetime.datetime(dt.year,dt.month,dt.day,0,0): return True
		return False
	@property
	def sender_name(self):
		return mark_safe("%s&nbsp;%s" % (self.sender.first_name, self.sender.last_name))
	@property
	def recipient_name(self):
		return mark_safe("%s&nbsp;%s" % (self.recipient.first_name, self.recipient.last_name))
	
	def is_marked_for_moderator(self,desired=True):
		return (self.flagged_date == None) != desired 
	def is_deleted(self,desired=True):
		return (self.deleted_date == None) != desired 
	def is_read(self,desired=True):
		return (self.read_date == None) != desired 	
	def is_sent(self,desired=True):
		return (self.send_date == None) != desired 
	def is_hidden(self,desired=True):
		return (self.hide_date == None) != desired
	
	def mark_sent(self):
		self.send_date = datetime.datetime.now()
	def mark_read(self):
		self.read_date = datetime.datetime.now()
	def mark_unread(self):
		self.read_date = None
	def mark_deleted(self):
		self.deleted_date = datetime.datetime.now()
	def mark_undeleted(self):
		self.deleted_date = None
	def mark_flagged_for_moderator(self):
		self.flagged_date = datetime.datetime.now()
	def mark_reviewed_by_moderator(self):
		self.flagged_date = None
	def mark_hidden(self):
		self.hide_date = datetime.datetime.now()
	def mark_unhidden(self):
		self.hide_date = None
		
class ForumPrivateMessageForm(forms.ModelForm):
	class Meta:
		model = ForumPrivateMessage
		fields = ['recipient','subject','message']
	def __init__(self, sender, *args, **kwargs):
		super(ForumPrivateMessageForm, self).__init__(*args, **kwargs)
		self.sender = sender or None
		self.fields["recipient"].widget = forms.RadioSelect()
		self.fields['message'].widget.attrs = {'class':'mceNoToggle'}
		CPs = CourseParticipant.objects.none()
		for course in self.instance.forum.courses.all():
			CPs |= CourseParticipant.find(course,keep_queryset=True)
		block_list = []
		if sender:
			block_list = [block.blocked for block in sender.block_list.all()]
		self.fields["recipient"].choices = natural_sort([(cp.user.id,"%s %s" % (cp.user.first_name, cp.user.last_name)) for cp in CPs if cp.user not in block_list],key=lambda item: item[1])
	def clean_recipient(self):
		block_list = [block.blocked for block in self.sender.block_list.all()] if self.sender else []
		recipient = self.cleaned_data['recipient']
		if recipient in block_list:
			raise forms.ValidationError("Sending messages to users on your block list is prohibited")
		# Always return the cleaned data, whether you have changed it or not.
		return recipient
